/** Progetto PRAISED **/
package it.uniroma3.dia.praised.brenda.crawler;

import it.uniroma3.dia.praised.brenda.model.Enzima;
import it.uniroma3.dia.praised.brenda.model.Letteratura;
import it.uniroma3.dia.praised.brenda.model.Sinonimo;
import it.uniroma3.dia.praised.brenda.validator.Validator;
import it.uniroma3.dia.praised.brenda.wrapper.WrapperBrenda;
import it.uniroma3.dia.praised.brenda.wrapper.WrapperPubMed;
import it.uniroma3.dia.praised.exception.CrawlerException;
import it.uniroma3.dia.praised.exception.WrapperException;
import it.uniroma3.dia.praised.factory.XmlReaderFactory;
import it.uniroma3.dia.praised.util.ExtractionResult;
import it.uniroma3.dia.praised.util.SerializzaXML;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.sax.SAXSource;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.XMLReader;

/**
 * CrawlerBrendaWorker : Classe responsabile della creazione dell'Enzima e dell'esplorazione di tutti gli URL
 * dai quali si possono ottenere informazioni aggiuntive per l'enzima stesso
 * 
 * @author Accosta Augusto
 * @author Lopez Elisabetta
 * @see it.uniroma3.dia.praised.factory.XmlReaderFactory
 * @see it.uniroma3.dia.praised.util.SerializzaXML
 * @see it.uniroma3.dia.praised.util.Utils
 * @see it.uniroma3.dia.praised.brenda.model.Enzima
 * @see it.uniroma3.dia.praised.brenda.model.Letteratura
 * @see it.uniroma3.dia.praised.brenda.model.Sinonimo
 * @see it.uniroma3.dia.praised.brenda.validator.Validator
 * @see it.uniroma3.dia.praised.brenda.wrapper.WrapperBrenda
 * @see it.uniroma3.dia.praised.brenda.wrapper.WrapperPubMed
 * @version 1.0
 *
 */
public class CrawlerBrendaWorker {

	private static final ConcurrentMap<String, Letteratura> cacheLetterature = new ConcurrentHashMap<String, Letteratura>();
	
	private XMLReader reader;
	
	private WrapperPubMed wrapperPubMed;
	
	private WrapperBrenda wrapperBrenda;
	
	/**
	 * Costruttore no-arg
	 */
	public CrawlerBrendaWorker() throws CrawlerException {
		
		try {
			this.wrapperBrenda = new WrapperBrenda();
			this.wrapperPubMed = new WrapperPubMed();
			
			this.reader = XmlReaderFactory.newXmlReader();
			
		} catch (SAXNotRecognizedException e) {
			throw new CrawlerException(e.getMessage());
		} catch (SAXNotSupportedException e) {
			throw new CrawlerException(e.getMessage());
		} catch (WrapperException e) {
			throw new CrawlerException(e.getMessage());
		}
		
	}
	
	/**
	 * Metodo che gestisce la creazione ed il popolamento dell'enzima
	 * 
	 * @param url - url corrente
	 * @param badlist - lista su cui verranno scritti gli url non processati
	 * @throws CrawlerException 
	 */
	public void run(URL url, List<URL> badlist) throws CrawlerException {
		
		Enzima enzima = new Enzima();
		ExtractionResult infoExtraction = new ExtractionResult(false, false);
		URL detailedURL = null;
		
		try {
			/* Estrazione dell'url da navigare */
			detailedURL = getDetailedURL(url);
			if(detailedURL == null) {
				badlist.add(url);
				return;
			}
			
			long start = System.currentTimeMillis(); //Istante iniziale di processamento 

			/* Costruzione dell'enzima */
			this.wrapperBrenda.wrapPageIntoEnzima(enzima, detailedURL, infoExtraction); // Tecnica del Side-Effect
			
			/* Eventuale scrittura dell'url sulla badlist in caso di mancata estrazione */
			if(!infoExtraction.getExtractionResult()) {
				badlist.add(url);
				return;
			}
			
			long end = System.currentTimeMillis(); //Istante finale di processamento 
			System.out.println("PROCESSED: " + enzima.getID() + " (" + enzima.getSinonimi().size() + ") " + " TIME: " + (end - start)); // Vedo quanto ci mette una pagina ad estrarre le info

			/* Estrazione letterature */
			pubMedIdExtraction(enzima);

			/* Salvataggio dell'enzima in un file xml */
			SerializzaXML save = new SerializzaXML(enzima);
			save.serializza(infoExtraction);
			
			/* Verifica risultato */
			if(!infoExtraction.getCreationResult()) {
				badlist.add(url);
			}

		} catch (WrapperException e) {
			return;	// throw new CrawlerException(e.getMessage());
		} catch (CrawlerException e) {
			return; // throw new CrawlerException(e.getMessage());
		} catch (MalformedURLException e) {
			throw new CrawlerException(e.getMessage());
		}

	}

	/** Metodo che estrae il PubMed id relativo ad ognuno dei sinonimi dell'enzima
	 * 
	 * @param enzima
	 * @throws MalformedURLException
	 */
	private void pubMedIdExtraction(Enzima enzima) throws MalformedURLException {
		
		/* Scansione dei sinonimi e recupero della letteratura */
		for (Sinonimo sinonimo : enzima.getSinonimi()) {
			
			/* A partire dalla mappa brendaId -> pubmedId creata in precedenza, per ogni chiave (brendaId) 
			 * si cerca la corrispondente letteratura sul sito PubMed */
			for(Map.Entry<String, Letteratura> brendaIDToLetteratura : sinonimo.getLetterature().entrySet()) { // scorro la mappa
				String brendaID = brendaIDToLetteratura.getKey();
				
				/* La letteratura viene aggiunta alla mappa solo se assente */
				Letteratura letteratura;
				if(cacheLetterature.containsKey(brendaID)) {
					letteratura = cacheLetterature.get(brendaID);
				} else {
					String brendaPubMedURL = buildBrendaPubMedURL(enzima.getID(), brendaID);
					letteratura = new Letteratura();
					this.wrapperPubMed.wrapPageIntoLetteratura(letteratura, brendaID, new URL(brendaPubMedURL)); // Side-Effect
					cacheLetterature.putIfAbsent(brendaID, letteratura);
				}
				
				brendaIDToLetteratura.setValue(letteratura);
			}
		}
	}
	
	/**
	 * Metodo che esplora la pagina assegnatagli alla creazione dell'istanza del CrawlerBrendaWorker.
	 * Va a cercare nella pagina in questione l'URL che porta alla pagina da cui realmente posso
	 * estrarre tutte le informazioni relative all'Enzima.
	 * 
	 * @param url
	 * @return Il link del sito che contiene le info da estrarre o <code>null</code> (se ci sono errori nella creazione del DOM)
	 * @throws CrawlerException 
	 */
	private URL getDetailedURL(URL url) throws CrawlerException {
		
		/* Costruzione dell'albero relativo al DOM della pagina da analizzare */
		Transformer transformer;
		
		try {
			transformer = TransformerFactory.newInstance().newTransformer();
			DOMResult result = new DOMResult();
			transformer.transform(new SAXSource(reader, new InputSource(url.openStream())), result);
			
			/* Recupero di tutte le ancore presenti nell'albero */
			XPathFactory factory = XPathFactory.newInstance();
			XPath xPath=factory.newXPath();
			String pattern = "//a";
			NodeList ancore = (NodeList) xPath.evaluate(pattern, result.getNode(), XPathConstants.NODESET);
			
			/* Prendo il primo elemento della lista di ancore che contiene l'url necessario per la navigazione */
			String urlDaNavigare = ancore.item(1).getAttributes().getNamedItem("href").getNodeValue();
			
			/* Modifica dell'url in modo da ottenere un url che rappresenta direttamente il contenuto dei frame */
			urlDaNavigare = urlDaNavigare.replace("result_flat", "flat_result");
			String urlEffettiva = urlDaNavigare.substring(2) + "&organism_list=&Suchword=&UniProtAcc=";
					
			return Validator.buildURL(url, urlEffettiva);
			
		} catch (TransformerConfigurationException e) {
			throw new CrawlerException(e);
		} catch (TransformerFactoryConfigurationError e) {
			throw new CrawlerException(e);
		} catch (TransformerException e) {
			throw new CrawlerException(e);
		} catch (IOException e) {
			throw new CrawlerException(e);
		} catch (XPathExpressionException e) {
			throw new CrawlerException(e);
		} catch (Exception e) {
			throw new CrawlerException(e);
		}
		
	}
	
	/**
	 * Metodo che costruisce staticamente l'URL relativo alle letterature di Brenda.
	 * 
	 * @param idEnzima
	 * @param brendaID
	 * @return
	 */
	private String buildBrendaPubMedURL(String idEnzima, String brendaID) {
		return "http://www.brenda-enzymes.org/literature/lit.php4?e=" + idEnzima + "&r=" + brendaID;
	}

}
